#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "../common/general.h"
#include "../common/minilog.h"

#define VERBOSE		INFO | RESULT | WARNING | ERROR | CRITICAL
#define LOGGING		INFO | RESULT | PROCESS
#define COMMENT		"Genetic Algorithm, Binary-Encoding\nRandom Generator: Mersenne twister\nMutation uses probabily"
#define LOGFILENAME	"GABC_02.gabc"
#define ALGORITHM	"GABC_02"

/********************************************************************************
	Genetic Algorithm Parameters
********************/
#define max_iterations			50000
#define population_size			100
#define test_function			g02Rosenbrock
#define precision_digits		8
#define new_population			5
#define elite_size				2
#define mutation_rate			0.0005
#define elite_repetition_size	2
#define ELITE_REPETITION		{0,4,4, 4,10,3}
#define sample_step				1e-10
#define eval_stamp				100000
/*
#define dimension			100
#define lower_bound			-1000.0
#define upper_bound			+1000.0
*/
#include "../benchmark/group02fn.h"

/********************************************************************************
	Genetic Algorithm Global Variables
********************/
struct solution {
	char* bit_stream;
	double fitness;
};
struct solution* population;
struct solution* elite;
int elite_repetition[elite_repetition_size*3]=ELITE_REPETITION;
int* crossover_list;
int hasElite;
int nvarbits;
int nvecbits;
double precision;
double* last_x;
double* power_two;
double time_stamp;
int eval_counter;
char unique_logfilename[256];
#include "../common/common.h"
#include "../common/encoding.h"

/********************************************************************************
	Subordinate Functions
********************/
void copy(struct solution from,struct solution* to){
	int i;
	to->fitness=from.fitness;
	for(i=0;i<nvecbits;i++)
		to->bit_stream[i]=from.bit_stream[i];
}
int solComp(const void* a,const void* b) {
	double pa,pb;
	pa=((struct solution*)a)->fitness;
	pb=((struct solution*)b)->fitness;
	if (pa>pb) return 1;
	else if (pa<pb) return -1;
	return 0;
}

/********************************************************************************
	Genetic Algorithm Functions
********************/
void eval(struct solution *sol){
	vector_bin2dec_range(sol->bit_stream,last_x);
	sol->fitness=func(last_x);
	eval_counter++;
}

void evaluate(){
	int i;
	for(i=0;i<population_size;i++)
		eval(&population[i]);
	qsort(population,population_size,sizeof(struct solution),solComp);
}
int init() {
	int i;
	eval_counter=0;
	hasElite=0;
	build_min_vector();
	if(precision_digits)
		precision=1.0/pow(10,precision_digits);
	else precision=0.0;
	cal_nvarbits();
	nvecbits=dimension*nvarbits;

	population=(struct solution*) malloc(sizeof(struct solution)*population_size);
	elite=(struct solution*) malloc(sizeof(struct solution)*elite_size);
	for(i=0;i<elite_size;i++)
		elite[i].bit_stream=(char*) malloc(nvecbits);
	crossover_list=(int*) malloc(sizeof(int)*population_size);
	power_two=(double*) malloc(sizeof(double)*(nvarbits+1));
	last_x=(double*) malloc(sizeof(double)*dimension);
	power_two[0]=1.0;
	for(i=1;i<=nvarbits;i++) {
		power_two[i]=power_two[i-1]*2.0;
	}
	for(i=0;i<population_size;i++)
		population[i].bit_stream=(char*) malloc(nvecbits);
}
int populate() {
	int i,j;
	for(i=0;i<population_size;i++){
		population[i].bit_stream=(char*) malloc(nvecbits);
		for(j=0;j<nvecbits;j++)
			population[i].bit_stream[j]=randint3(2);
	}
}
void selection() {
	int i,j,k,bottom;
	bottom=population_size;

	if (hasElite) {
		for(i=0;i<elite_size;i++)
			copy(elite[i],&population[--bottom]);
		qsort(population,population_size,sizeof(struct solution),solComp);
		bottom=population_size;
	}
	

	for(i=0;i<elite_size;i++)
		copy(population[i],&elite[i]);
	hasElite=1;

	for(i=0;i<new_population;i++){
		bottom--;
		for(j=0;j<nvecbits;j++)
			population[bottom].bit_stream[j]=randint3(2);
	}
	
	for(i=0;i<elite_repetition_size;i++) {
		for(j=elite_repetition[i*3];j<elite_repetition[(i*3)+1];j++) {
			for(k=0;k<elite_repetition[(i*3)+2]-1;k++) {
				copy(population[j],&population[--bottom]);
			}
		}
	}
}
int crossover() {
	int i,j;
	int b,e,t;
	int index;
	int flag;
	b=randint3(nvecbits);
	e=randint3(nvecbits);
	while (b==e) e=randint3(nvecbits);
	if (b>e) {t=b;b=e;e=t;}
	for(i=0;i<population_size;i++){
		t=randint3(population_size);
		flag=1;
		while (flag){
			t=randint3(population_size);
			flag=0;
			for(j=0;j<i;j++)
				if(crossover_list[j]==t) {
					flag=1;
					break;
				}
		}
		crossover_list[i]=t;
	}
	for(i=0;i<population_size-1;i=i+2){
		for(j=b;j<e;j++) {
			t=population[crossover_list[i]].bit_stream[j];
			population[crossover_list[i]].bit_stream[j]=population[crossover_list[i+1]].bit_stream[j];
			population[crossover_list[i+1]].bit_stream[j]=t;
		}
	}
	return 0;
}
int mutation(){
	int i,j;
	for(i=0;i<population_size;i++){
		for(j=0;j<nvecbits;j++){
			if(random3()>=mutation_rate) continue;
			if(population[i].bit_stream[j]==0)
				population[i].bit_stream[j]=1;
			else
				population[i].bit_stream[j]=0;
		}
	}
	return 0;
}
int stopping_criteria(){
	return 0;
}

/********************************************************************************
	Display and Debug Functions
********************/
void show_info(FILE* out){
	int i;
	fprintf(out,"/*\n%s\n*/\n",COMMENT);
	L(INFO)fprintf(out,"Logging Filename                 : %s\n",unique_logfilename);
	fprintf(out,"Algorithm                        : %s\n",ALGORITHM);
	fprintf(out,"Test Function                    : %s\n",func_name);
	fprintf(out,"Number of Variables              : %d\n",dimension);
	fprintf(out,"Number of Bits per Variable      : %d\n",nvarbits);
	fprintf(out,"Number of Bits per Vector        : %d\n",nvecbits);
	fprintf(out,"Variables Lower Bound            : %.15f\n",lower_bound);
	fprintf(out,"Variables Upper Bound            : %.15f\n",upper_bound);
	fprintf(out,"Variables Precision              : %.15f\n",precision);
	fprintf(out,"Maximum Iterations               : %d\n",max_iterations);
	fprintf(out,"Population Size                  : %d\n",population_size);
	fprintf(out,"New Population in Each Generation: %d\n",new_population);
	fprintf(out,"Elite Repetition                 : %d\n",elite_size);
	fprintf(out,"Mutation Rate                    : %.4f\n",mutation_rate);
	fprintf(out,"Elite Group Size                 : %d\n",elite_repetition_size);
	for(i=0;i<elite_repetition_size;i++)
		fprintf(out,\
			"G[%02d]                            : b=%02d, e=%02d, t=%02d\n",\
			i,elite_repetition[i*3],elite_repetition[(i*3)+1],elite_repetition[(i*3)+2]);
	fprintf(out,"-------------------------------------------------------------\n");
}

/********************************************************************************
	Program Entry Point
********************/
int main(){
	int n=0;
	int i;
	double last_best_fitness=1e200;
	double time_stamp;
	double dist;
	clock_t start;
/*	srand(time(NULL)); */
	mt_init();
	get_unique_filename(LOGFILENAME,unique_logfilename);
	minilog_start(unique_logfilename,VERBOSE,LOGGING);
	init();
	V(INFO)show_info(stdout);
	L(INFO)show_info(LOGFILE);
	start=clock();
	populate();
	evaluate();
	while(n<max_iterations){
		selection();
		crossover();
		mutation();
		evaluate();
		eval(&population[0]);eval_counter--;
		dist=euclid_dist(last_x,min_vector);
//		VP(PROCESS,"[%06d] f=%.15f\td=%.15f             \r",n,population[0].fitness,dist);
		VP(PROCESS,"[%07d] f=%.15f\td=%.15f          \r",eval_counter,population[0].fitness,dist);
		if(fabs(population[0].fitness-last_best_fitness)>=sample_step || eval_counter%eval_stamp==0) {
//			LP(PROCESS,"%06d\t%.15f\t%.15f\n",n,population[0].fitness,dist);
			LP(PROCESS,"%07d\t%.15f\t%.15f\n",eval_counter,population[0].fitness,dist);
			last_best_fitness=population[0].fitness;
		}
		n++;
	}
	time_stamp=(double) (clock()-start)/CLOCKS_PER_SEC;
//	LP(PROCESS,"%06d\t%.15f\t%.15f\n",--n,population[0].fitness,dist);
	LP(PROCESS,"%07d\t%.15f\t%.15f\n",eval_counter,population[0].fitness,dist);
	VP(PROCESS,"\n");
	LOG(RESULT,"-----------------\n");

	LOG(RESULT,"Best Fitness           : %.15f\n",population[0].fitness);
	LOG(RESULT,"Closest Distance       : %.15f\n",dist);
	LOG(RESULT,"Time                   : %05.2fs\n",time_stamp);
	LOG(RESULT,"-----------------\n");
	for(i=0;i<dimension;i++){
		LOG(RESULT,"x[%d]= %.15f\n",i,last_x[i]);
	}
	minilog_stop();
	return 0;
}
